﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using FANN.Net;

namespace Сlassifier_Tool_For_OpenCV
{
    public class Ann
    {
        public FANN.Net.NeuralNet Net;

        public FANN.Net.TrainingData Data;

        public FANN.Net.TrainingData ValidateData;

        public int Epoch = 0;
        public int MaxEpoch = 1000;
        public int StaepSave = 10;
        public string DataFileName;
        public bool Run;
        private List<Item> _items;

        public bool OpenData(string file, bool memory = true)
        {
            if (File.Exists(file))
            {
                if (memory)
                {
                    try
                    {
                        Data = new FANN.Net.TrainingData();
                        Data.ReadTrainFromFile(file);
                        DataFileName = file;
                        return true;
                    }
                    catch
                    {
                    }
                }
                else
                {

                    try
                    {
                        var r = ReadTrainFromFile(file);
                        if (r != null)
                        {
                            Data = r;
                            DataFileName = file;
                            return true;
                        }
                    }
                    catch
                    {
                    }
                }
            }
            return false;

        }

        public bool OpenValidateData(string file, bool memory = true)
        {
            if (File.Exists(file))
            {
                if (memory)
                {
                    try
                    {
                        ValidateData = new FANN.Net.TrainingData();
                        ValidateData.ReadTrainFromFile(file);

                        return true;
                    }
                    catch
                    {
                    }
                }
                else
                {

                    try
                    {
                        var r = ReadTrainFromFile(file);
                        if (r != null)
                        {
                            ValidateData = r;

                            return true;
                        }
                    }
                    catch
                    {
                    }
                }
            }
            return false;

        }

        private TrainingData ReadTrainFromFile(string file)
        {
            if (File.Exists(file))
            {
                var date = new TrainingData();
                using (StreamReader str = new StreamReader(file, Encoding.Default))
                {
                    var s = str.ReadLine();
                    var c = s.Split(' ').Select(t => Convert.ToUInt32(t)).ToList();

                    double[][] data = new double[c[0]][];
                    double[][] output = new double[c[0]][];
                    int count = 0;
                    string temp = null;
                    int sc = 0;
                    while (str.Peek() >= 0)
                    {

                        temp = str.ReadLine();
                        if (count%2 == 0)
                        {
                            data[count/2] =
                                temp.Split(' ')
                                    .Select(t => Convert.ToDouble(t, CultureInfo.InvariantCulture))
                                    .ToArray();
                            sc += data[count/2].Length*8;
                        }
                        else
                        {
                            output[count/2] = temp.Split(' ').Select(t => Convert.ToDouble(t)).ToArray();
                        }
                        count++;

                    }



                    date.SetTrainData((uint) data.Length, c[1], data, c[2], output);

                }
                return date;
            }
            return null;
        }

        private uint[] _laers;
        private ActivationFunction _activationFunctionHidden;
        private ActivationFunction _activationFunctionOutput;
        private TrainingAlgorithm _trainingAlgorithm;

        public void CreateNet(uint[] laers, ActivationFunction activationFunctionHidden,
            ActivationFunction activationFunctionOutput, TrainingAlgorithm trainingAlgorithm)
        {
            Net = new NeuralNet();
            _laers = laers;
            _activationFunctionHidden = activationFunctionHidden;
            _activationFunctionOutput = activationFunctionOutput;
            _trainingAlgorithm = trainingAlgorithm;
            Net.CreateStandardArray(laers);
            Net.SetActivationFunctionHidden(activationFunctionHidden);
            Net.SetActivationFunctionOutput(activationFunctionOutput);
            Net.SetTrainingAlgorithm(trainingAlgorithm);
            Net.SetActivationSteepnessHidden(0.5);
            Net.SetActivationSteepnessOutput(0.5);
            Net.SetBitFailLimit(0.035);
            Net.SetLearningMomentum(0);
            Net.SetLearningRate(0.7f);
            Net.SetTrainErrorFunction(ErrorFunction.Linear);
            Net.SetTrainStopFunction(StopFunction.MSE);

        }

        public void CreateNet(string file)
        {
            Net = new NeuralNet();
            Net.CreateFromFile(file);
        }

        public bool LoadAnn(string file)
        {
            if (File.Exists(file))
            {
                Net = new NeuralNet();
                Net.CreateFromFile(file);
                return true;
            }
            return false;
        }

        public void StartSimpleTrein(int maxEpoch, int staepSave)
        {
            Epoch = 0;
            _items = new List<Item>();
            MaxEpoch = maxEpoch;
            StaepSave = staepSave;
            if (Data != null && Net != null)
            {
                var t = new System.Threading.Thread(SimpleTrein);
                t.SetApartmentState(ApartmentState.STA);
                t.Start();
            }
        }

        public void StartValidateTrein(int maxEpoch, int staepSave)
        {
            Epoch = 0;
            _items = new List<Item>();
            MaxEpoch = maxEpoch;
            StaepSave = staepSave;
            if (Data != null && Net != null)
            {
                var t = new System.Threading.Thread(ValidateTrein);
                t.SetApartmentState(ApartmentState.STA);
                t.Start();
            }
        }

        private void SimpleTrein()
        {
            double v = 0;
            Run = true;
            while (Epoch < MaxEpoch && Run)
            {
                v = Net.TrainEpoch(Data);

                OnNewStepSimpleTrain(Epoch, v, 0);

                if (Epoch%StaepSave == 0)
                {
                    var i = new Item(Epoch, v, Net, DataFileName);

                    _items.Add(i);


                    var m = _items.Min(t => t.Value);
                    if (v <= m)
                    {
                        i.TempSave();
                    }
                }
                if (v < 0.000000001) break;
                Epoch++;
            }
            OnNewStepSimpleTrain(Epoch, v, 0);
            _items.Add(new Item(Epoch, v, Net, DataFileName));
            if (EndTrain != null)
                EndTrain(_items);

        }

        private void ValidateTrein()
        {
            double v = 0;
            double vv = 0;
            Run = true;
            while (Epoch < MaxEpoch && Run)
            {
                v = Net.TrainEpoch(Data);
                vv = Net.TestData(ValidateData);
                OnNewStepSimpleTrain(Epoch, v, vv);

                if (Epoch%StaepSave == 0)
                {
                    var i = new Item(Epoch, v, Net, DataFileName);

                    _items.Add(i);


                    var m = _items.Min(t => t.Value);
                    if (v <= m)
                    {
                        i.TempSave();
                    }
                }
                if (v < 0.000000001) break;
                Epoch++;
            }
            OnNewStepSimpleTrain(Epoch, v, vv);
            _items.Add(new Item(Epoch, v, Net, DataFileName));
            if (EndTrain != null)
                EndTrain(_items);

        }


        public delegate void StepEvent(int step, double errorOnData, double errorOnValidate);


        public event StepEvent NewStepSimpleTrain;
        public event StepEvent NewStepStepTrain;



        public delegate void EndTrainEvent(List<Item> items);


        public event EndTrainEvent EndTrain;

        protected virtual void OnNewStepSimpleTrain(int step, double errorondata, double erroronvalidate)
        {
            var handler = NewStepSimpleTrain;
            if (handler != null) handler(step, errorondata, erroronvalidate);
        }

        public string Method
        {
            get { return Net.GetTrainingAlgorithm().ToString(); }
            set { Net.SetTrainingAlgorithm((TrainingAlgorithm) Enum.Parse(typeof (TrainingAlgorithm), value, true)); }
        }

        public string HidenFunction
        {
            get { return Net.GetActivationFunction(0, 0).ToString(); }
            set
            {
                Net.SetActivationFunctionHidden(
                    (ActivationFunction) Enum.Parse(typeof (ActivationFunction), value, true));
            }
        }

        public string OutputFunction
        {
            get { return Net.GetActivationFunction(Convert.ToInt32(Net.GetNumLayers() - 1), 0).ToString(); }
            set
            {
                Net.SetActivationFunctionOutput(
                    (ActivationFunction) Enum.Parse(typeof (ActivationFunction), value, true));
            }
        }

        public List<List<List<float>>> Test(string file)
        {
            var testdata = ReadFile(file);

            List<List<List<float>>> ret = new List<List<List<float>>>();
            foreach (var v in testdata[2])
                ret.Add(new List<List<float>>()
                {
                    new List<float>(),
                    new List<float>(),
                    new List<float>()
                });
            for (var i = 1; i < testdata.Count; i = i + 2)
            {
                var input = testdata[i].Select(t1 => (double) t1).ToArray();

                var o = Net.Run(input);

                double max = 0;
                var z = 0;
                for (var j = 0; j < o.Length; j++)
                {
                    if (o[j] > max)
                    {
                        max = o[j];
                        z = j;
                    }

                }
                var d = 0;
                for (var j = 0; j < testdata[i + 1].Count; j++)
                    if (testdata[i + 1][j] == 1)
                        d = j;
                ret[d][d == z ? 0 : 1].Add((float) max);
                if (d != z)
                    ret[d][2].Add(z);

            }
            return ret;
        }

        private List<List<float>> ReadFile(string file)
        {
            var ret = new List<List<float>>();
            string line;
            var stream = new StreamReader(file);
            while ((line = stream.ReadLine()) != null)
            {
                List<float> list = new List<float>();
                try
                {
                    list.AddRange(
                        line.Split(' ')
                            .Where(t => t != "")
                            .Select(s => (float) Convert.ToDouble(s, CultureInfo.InvariantCulture)));
                }
                catch (Exception ex)
                {

                }
                ret.Add(list);
            }
            stream.Close();
            return ret;
        }


        public void StopSimple()
        {
            Run = false;
        }
        private double Test(NeuralNet n, IList<string> str)
        {
            double v = 0;

            var count = 0;
            for (var i = 0; i < str.Count; i = i + 2)
            {
                var sy = str[i].TrimEnd(' ').Split(' ');
                var input = new double[sy.Length];
                count++;
                for (var y = 0; y < sy.Length; y++)
                    input[y] = Convert.ToDouble(sy[y], CultureInfo.InvariantCulture);

                var o = n.Run(input);
                double max = 0;

                var z = 0;
                for (var j = 0; j < o.Length; j++)
                {
                    if (o[j] > max)
                    {
                        max = o[j];
                        z = j;
                    }

                }
                var d = 0;
                for (var j = 0; j < str[i + 1].Length; j += 2)
                    if (str[i + 1][j] == '1')
                        d = j / 2;
                if (d == z)
                    v++;
            }


            return v / count;


        }

        public void StartStepTrein(string dataFile, int minEdu, int maxEdu, int stepEdu, int maxEpo)
        {
            System.Threading.Thread t = new Thread(StepTrein);
            t.Start(new List<object> {dataFile, minEdu, maxEdu, stepEdu, maxEpo});


        }
        public void StepTrein(object o) 
        {
            List<object> oo = (List<object>) o;
            string dataFile = (string) oo[0];
            int minEdu=(int)oo[1];
               int maxEdu=(int)oo[2];
               int stepEdu = (int)oo[3]; 
            int maxEpo = (int)oo[4];
            var b = File.ReadAllLines(dataFile).ToList();
            b.RemoveAt(0);
            List<List<int>> data = new List<List<int>>();
            for (int i = 0; i < b[1].TrimEnd(' ').Split(' ').Length; i++)
                data.Add(new List<int>());
            for (int i = 0; i < b.Count; i += 2)
            {
                var ou = b[i + 1].Split(' ');

                var d = 0;
                for (var j = 0; j < ou.Length; j++)
                    if (ou[j] == "1")
                        d = j;

                data[d].Add(i/2);
            }


            var r = new Random();

            for (var i = minEdu/100.0;
                i <= maxEdu/100.0;
                i += stepEdu/100.0)
            {
                CreateNet(_laers, _activationFunctionHidden, _activationFunctionOutput, _trainingAlgorithm);
                ;
                var d = new List<string>();
                var test = new List<string>();
                var cop = new bool[b.Count/2];
                for (var j = 0; j < b.Count/2; j++)
                    cop[j] = false;
                foreach (var dd in data)
                {
                    for (int l = 0; l < dd.Count*i; l++)
                        cop[dd[l]] = true;
                }

                for (var j = 0; j < b.Count/2; j++)
                    if (cop[j])
                    {
                        d.Add(b[j*2]);
                        d.Add(b[j*2 + 1]);
                    }
                    else
                    {
                        test.Add(b[j*2]);
                        test.Add(b[j*2 + 1]);
                    }
                d.Insert(0,
                    Convert.ToString(d.Count/2) + " " + Convert.ToString(d[0].TrimEnd(' ').Split(' ').Length) + " " +
                    Convert.ToString(d[1].TrimEnd(' ').Split(' ').Length));
                var temp = i + " " + Guid.NewGuid() + ".dat";
                File.WriteAllLines(temp, d.ToArray());

                //_date.Dispose();
                Thread.Sleep(1000);
                var ddd = new TrainingData();
                ddd.ReadTrainFromFile(temp);
                d.RemoveAt(0);


                for (var k = 0; k < maxEpo; k++)
                {

                    var v = Net.TrainEpoch(ddd);
                    OnNewStepSimpleTrain(k, v, 0);
                }
                NewStepStepTrain(Convert.ToInt32(i * 100), Test(Net, d), Test(Net, test));

            }

        }
    }
}
